/*
* Copyright (c) 2006-2018, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "drv_dwmci.h"
#include <rthw.h>
#include "board.h"
#include "rk3568_iomux.h"
#include "rockchip_reg.h"
#include "rockchip_dwmci.h"
#include "bouncebuf.h"
#include "memalign.h"
#include <drivers/mmcsd_core.h>

#define BSP_USING_SDMMC

#define DWMCI_FIFO_DEPTH 0x100

struct dwmci_host
{
    struct rt_mmcsd_host *host;
    rt_ubase_t base;
    const char *name;
    rt_bool_t fifo_mode;
    rt_uint32_t fifoth_val;
    rt_uint32_t clock;
    rt_bool_t stride_pio;
};

struct dwmci_idmac
{
    rt_uint32_t flags;
    rt_uint32_t cnt;
    rt_uint32_t addr;
    rt_uint32_t next_addr;
} rt_align(ARCH_DMA_MINALIGN);

struct mmc_cmd
{
    rt_uint16_t cmdidx;
    rt_uint32_t resp_type;
    rt_uint32_t cmdarg;
    rt_uint32_t response[4];
};

struct mmc_data
{
    union {
        char *dest;
        const char *src;
    };
    rt_uint32_t flags;
    rt_uint32_t blocks;
    rt_uint32_t blocksize;
};

#if defined(BSP_USING_SDMMC)
static struct dwmci_host sdmmc =
{
    .base = 0xFE2B0000,
    .name = "sdmmc",
    .fifo_mode = RT_FALSE,
    .stride_pio = RT_FALSE
};
#endif /* BSP_USING_SDMMC */

#if defined(BSP_USING_EMMC)
static struct dwmci_host emmc =
{
    .base = RK3308_EMMC_BASE,
    .name = "emmc",
    .fifo_mode = RT_FALSE,
    .stride_pio = RT_FALSE
};
#endif /* BSP_USING_EMMC */

/*
* Currently it supports read/write up to 8*8*4 Bytes per
* stride as a burst mode. Please note that if you change
* MAX_STRIDE, you should also update dwmci_memcpy_fromio
* to augment the groups of {ldm, stm}.
*/
#if defined(CONFIG_ARM) && defined(CONFIG_CPU_V7)
void noinline dwmci_memcpy_fromio(void *buffer, void *fifo_addr)
{
    __asm__ __volatile__ (
        "push {r2, r3, r4, r5, r6, r7, r8, r9}\n"
        "ldm r1, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "stm r0!, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "ldm r1, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "stm r0!, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "ldm r1, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "stm r0!, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "ldm r1, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "stm r0!, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "ldm r1, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "stm r0!, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "ldm r1, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "stm r0!, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "ldm r1, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "stm r0!, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "ldm r1, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "stm r0!, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "pop {r2, r3, r4, r5, r6,r7,r8,r9}\n"
        :::"memory"
    );
}

void noinline dwmci_memcpy_toio(void *buffer, void *fifo_addr)
{
    __asm__ __volatile__ (
        "push {r2, r3, r4, r5, r6, r7, r8, r9}\n"
        "ldm r0!, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "stm r1, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "ldm r0!, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "stm r1, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "ldm r0!, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "stm r1, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "ldm r0!, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "stm r1, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "ldm r0!, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "stm r1, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "ldm r0!, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "stm r1, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "ldm r0!, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "stm r1, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "ldm r0!, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "stm r1, {r2,r3,r4,r5,r6,r7,r8,r9}\n"
        "pop {r2, r3, r4, r5, r6,r7,r8,r9}\n"
        :::"memory"
    );
}
#else
void dwmci_memcpy_fromio(void *buffer, void *fifo_addr) {};
void dwmci_memcpy_toio(void *buffer, void *fifo_addr) {};
#endif

static void plat_delay(rt_uint32_t delay)
{
    while(delay--);
}

static inline void dwmci_writel(struct dwmci_host *host, int reg, rt_uint32_t val)
{
    writel(val, host->base + reg);
}

static inline rt_uint32_t dwmci_readl(struct dwmci_host *host, int reg)
{
    // rt_kprintf("%s:%03x:%08x", __func__, reg, val);
    return readl(host->base + reg);
}

static int dwmci_wait_reset(struct dwmci_host *host, rt_uint32_t value)
{
    rt_uint16_t timeout = 1000;
    rt_uint32_t ctrl = 0;

    dwmci_writel(host, DWMCI_CTRL, value);
    while(timeout--)
    {
        ctrl = dwmci_readl(host, DWMCI_CTRL);
        if (!(ctrl & DWMCI_CTRL_RESET_ALL))
            return 1;
    }
    return 0;
}

// #include <drivers/clk.h>
static int dwmci_setup_bus(struct dwmci_host *host, rt_uint32_t freq)
{
    rt_uint32_t div, status;
    int timeout = 10000;
    // rt_ubase_t sclk;

    if (freq == 0)
        return 0;

    if(freq < host->host->freq_min)
    {
        freq = host->host->freq_min;
    }

    if(freq > host->host->freq_max)
    {
        freq = host->host->freq_max;
    }

    // sclk = rt_clk_get_rate(rt_clk_find("clk_sdmmc0"));
    // sclk = rt_clk_set_rate(rt_clk_find("clk_sdmmc0"), freq);
    // if(sclk == freq)
    //     div = 0;
    // else
    //     div = DIV_ROUND_UP(sclk, 2 * freq);

    if(freq == 400*1000)
    {
        div = 125;
    }
    else
    {
        div = 0;
    }

    // rt_kprintf("sclk = %d 2*freq = %d\n", sclk, 2 * freq);
    // rt_kprintf("div = %d\n", div);

    dwmci_writel(host, DWMCI_CLKENA, DWMCI_CLKENA_CCLK_DISABLE);
    dwmci_writel(host, DWMCI_CLKSRC, 0);

    dwmci_writel(host, DWMCI_CLKDIV, div);
    dwmci_writel(host, DWMCI_CMD, DWMCI_CMD_WAIT_PRVDATA_COMPLETE |
        DWMCI_CMD_UPDATE_CLOCK_REGS_ONLY | DWMCI_CMD_START_CMD);

    timeout = 10000;
    do {
        status = dwmci_readl(host, DWMCI_CMD);
        if (timeout-- < 0) {
            rt_kprintf("%s: timeout\n", __func__);
            return (-RT_ETIMEOUT);
        }
    } while(status & DWMCI_CMD_START_CMD_MASK);

    /* enable clk and config clk low power */
    dwmci_writel(host, DWMCI_CLKENA, DWMCI_CLKENA_CCLK_ENABLE | DWMCI_CLKENA_CCLK_LOW_POWER_MASK);
    dwmci_writel(host, DWMCI_CMD, DWMCI_CMD_WAIT_PRVDATA_COMPLETE |
        DWMCI_CMD_UPDATE_CLOCK_REGS_ONLY | DWMCI_CMD_START_CMD);

    timeout = 10000;
    do {
        status = dwmci_readl(host, DWMCI_CMD);
        if (timeout-- < 0) {
            rt_kprintf("%s: timeout\n", __func__);
            return (-RT_ETIMEOUT);
        }
    } while(status & DWMCI_CMD_START_CMD_MASK);

    host->clock = freq;

    return RT_EOK;
}

static int dwmci_set_ios(struct dwmci_host *host, rt_uint32_t width, rt_uint32_t clock)
{
    rt_uint32_t ctype;

    dwmci_setup_bus(host, clock);

    switch (width)
    {
    case MMCSD_BUS_WIDTH_8:
        ctype = DWMCI_CTYPE_CARD_8BIT;
        break;
    case MMCSD_BUS_WIDTH_4:
        ctype = DWMCI_CTYPE_CARD_4BIT;
        break;
    default:
        ctype = DWMCI_CTYPE_CARD_1BIT;
        break;
    }

    dwmci_writel(host, DWMCI_CTYPE, ctype);

    return RT_EOK;
}

#define MMC_RSP_PRESENT (1 << 0)
#define MMC_RSP_136     (1 << 1)
#define MMC_RSP_CRC     (1 << 2)
#define MMC_RSP_BUSY    (1 << 3)
#define MMC_RSP_OPCODE  (1 << 4)

#define MMC_RSP_NONE    (0)
#define MMC_RSP_R1      (MMC_RSP_PRESENT | MMC_RSP_CRC | MMC_RSP_OPCODE)
#define MMC_RSP_R1b     (MMC_RSP_PRESENT | MMC_RSP_CRC | MMC_RSP_OPCODE | MMC_RSP_BUSY)
#define MMC_RSP_R2      (MMC_RSP_PRESENT | MMC_RSP_136 | MMC_RSP_CRC)
#define MMC_RSP_R3      (MMC_RSP_PRESENT)
#define MMC_RSP_R4      (MMC_RSP_PRESENT)
#define MMC_RSP_R5      (MMC_RSP_PRESENT | MMC_RSP_CRC | MMC_RSP_OPCODE)
#define MMC_RSP_R6      (MMC_RSP_PRESENT | MMC_RSP_CRC | MMC_RSP_OPCODE)
#define MMC_RSP_R7      (MMC_RSP_PRESENT | MMC_RSP_CRC | MMC_RSP_OPCODE)

static const rt_uint32_t resp_type_maps[9] =
{
    MMC_RSP_NONE, MMC_RSP_R1, MMC_RSP_R1b, MMC_RSP_R2, MMC_RSP_R3,
    MMC_RSP_R4, MMC_RSP_R6, MMC_RSP_R7, MMC_RSP_R5,
};

rt_inline rt_uint32_t dwmci_convert_resp_type(rt_uint32_t resp_type)
{
    return resp_type_maps[(resp_type & RESP_MASK)];
}

static void dwmci_set_idma_desc(struct dwmci_idmac *idmac, rt_uint32_t flags, rt_uint32_t cnt, rt_uint32_t addr)
{
    struct dwmci_idmac *desc = idmac;
    desc->flags = flags;
    desc->cnt = cnt;
    desc->addr = addr;
    desc->next_addr = (rt_ubase_t)rt_kmem_v2p(desc) + sizeof(struct dwmci_idmac);
}

static void dwmci_prepare_data(struct dwmci_host *host, struct mmc_data *data,
    struct dwmci_idmac *cur_idmac, void *bounce_buffer)
{
    unsigned long ctrl;
    unsigned int i = 0, flags, cnt, blk_cnt;
    rt_ubase_t data_start, data_end;

    blk_cnt = data->blocks;

    dwmci_wait_reset(host, DWMCI_CTRL_FIFO_RESET);

    data_start = (rt_ubase_t)cur_idmac;
    dwmci_writel(host, DWMCI_DBADDR, (rt_ubase_t)rt_kmem_v2p(cur_idmac));


    do {
        flags = DWMCI_IDMAC_OWN | DWMCI_IDMAC_CH;
        flags |= (i == 0) ? DWMCI_IDMAC_FS : 0;

        if (blk_cnt <= 8) {
            flags |= DWMCI_IDMAC_LD;
            cnt = data->blocksize * blk_cnt;
        } else
            cnt = data->blocksize * 8;

        #define PAGE_SIZE 4096
        dwmci_set_idma_desc(cur_idmac, flags, cnt, (rt_ubase_t)rt_kmem_v2p(bounce_buffer) + (i * PAGE_SIZE));

        if (blk_cnt <= 8)
            break;
        blk_cnt -= 8;
        cur_idmac++;
        i++;
    } while (1);

    data_end = (rt_ubase_t)cur_idmac;
    rt_hw_cpu_dcache_ops(RT_HW_CACHE_FLUSH, (void *)data_start, data_end + ARCH_DMA_MINALIGN - data_start);

    ctrl = dwmci_readl(host, DWMCI_CTRL);
    ctrl |= DWMCI_CTRL_USE_INTERNAL_DMAC | DWMCI_CTRL_DMA_ENABLE;
    dwmci_writel(host, DWMCI_CTRL, ctrl);

    ctrl = dwmci_readl(host, DWMCI_BMOD);
    ctrl |= DWMCI_BMOD_FB | DWMCI_BMOD_DE;
    dwmci_writel(host, DWMCI_BMOD, ctrl);

    dwmci_writel(host, DWMCI_BLKSIZ, data->blocksize);
    dwmci_writel(host, DWMCI_BYTCNT, data->blocksize * data->blocks);
}

#define MAX_STRIDE 64
static int dwmci_data_transfer(struct dwmci_host *host, struct mmc_data *data)
{
    int ret = 0;
    int reset_timeout = 100;
    rt_uint32_t timeout = 2400;
    rt_uint32_t status, ctrl, mask, size, i, len = 0;
    rt_uint32_t *buf = NULL;
    rt_tick_t start = rt_tick_get();
    rt_uint32_t fifo_depth = (((host->fifoth_val & DWMCI_FIFOTH_RX_WMARK_MASK) >> DWMCI_FIFOTH_RX_WMARK_SHIFT) + 1) * 2;
    rt_bool_t stride;

    size = data->blocksize * data->blocks / 4;
    /* Still use legacy PIO mode if size < 512(128 * 4) Bytes */
    stride = host->stride_pio && size > 128;
    if (data->flags == DATA_DIR_READ)
        buf = (unsigned int *)data->dest;
    else
        buf = (unsigned int *)data->src;

    for (;;) {
        mask = dwmci_readl(host, DWMCI_RINTSTS);
        /* Error during data transfer. */
        if (mask & (DWMCI_INTMASK_DATA_ERR | DWMCI_INTMASK_DATA_TOUT))
        {
            rt_kprintf("%s: data error!\n", __func__);

            dwmci_wait_reset(host, DWMCI_CTRL_RESET_ALL);
            dwmci_writel(host, DWMCI_CMD, DWMCI_CMD_WAIT_PRVDATA_COMPLETE |
                DWMCI_CMD_UPDATE_CLOCK_REGS_ONLY | DWMCI_CMD_START_CMD);

            do {
                status = dwmci_readl(host, DWMCI_CMD);
                if (reset_timeout-- < 0)
                    break;
                // udelay(100);
                plat_delay(50000);
            } while (status & DWMCI_CMD_START_CMD_MASK);

            if (!host->fifo_mode)
            {
                ctrl = dwmci_readl(host, DWMCI_BMOD);
                ctrl |= DWMCI_BMOD_SWR;
                dwmci_writel(host, DWMCI_BMOD, ctrl);
            }

            ret = (-RT_EINVAL);
            break;
        }

        if (host->fifo_mode && size)
        {
            len = 0;
            if (data->flags == DATA_DIR_READ && (mask & DWMCI_INTMASK_RXDR_MASK))
            {
                while (size)
                {
                    len = dwmci_readl(host, DWMCI_STATUS);
                    len = (len & DWMCI_STATUS_FIFO_COUNT_MASK) >> DWMCI_STATUS_FIFO_COUNT_SHIFT;
                    len = RK_MIN(size, len);
                    if (!stride)
                    {
                        /* Legacy pio mode */
                        for (i = 0; i < len; i++)
                            *buf++ = dwmci_readl(host, DWMCI_FIFO_BASE);
                        goto read_again;
                    }

                    /* dwmci_memcpy_fromio now bursts 256 Bytes once */
                    if (len < MAX_STRIDE)
                        continue;

                    for (i = 0; i < len / MAX_STRIDE; i++)
                    {
                        dwmci_memcpy_fromio(buf, (void *)(host->base + DWMCI_FIFO_BASE));
                        buf += MAX_STRIDE;
                    }

                    len = i * MAX_STRIDE;
read_again:
                    size = size > len ? (size - len) : 0;
                }
                dwmci_writel(host, DWMCI_RINTSTS, DWMCI_INTMASK_RXDR);
            }
            else if (data->flags == DATA_DIR_WRITE && (mask & DWMCI_INTMASK_TXDR_MASK))
            {
                while (size)
                {
                    len = dwmci_readl(host, DWMCI_STATUS);
                    len = fifo_depth - ((len & DWMCI_STATUS_FIFO_COUNT_MASK) >> DWMCI_STATUS_FIFO_COUNT_SHIFT);
                    len = RK_MIN(size, len);
                    if (!stride)
                    {
                        for (i = 0; i < len; i++)
                            dwmci_writel(host, DWMCI_FIFO_BASE, *buf++);
                        goto write_again;
                    }

                    /* dwmci_memcpy_toio now bursts 256 Bytes once */
                    if (len < MAX_STRIDE)
                        continue;

                    for (i = 0; i < len / MAX_STRIDE; i++)
                    {
                        dwmci_memcpy_toio(buf, (void *)(host->base + DWMCI_FIFO_BASE));
                        buf += MAX_STRIDE;
                    }

                    len = i * MAX_STRIDE;
write_again:
                    size = size > len ? (size - len) : 0;
                }
                dwmci_writel(host, DWMCI_RINTSTS, DWMCI_INTMASK_TXDR);
            }
        }

        /* Data arrived correctly. */
        if (mask & DWMCI_INTMASK_DTO_MASK)
        {
            ret = 0;
            break;
        }

        /* Check for timeout. */
        if ((rt_tick_get() - start) > timeout)
        {
            rt_kprintf("%s: timeout waiting for data!\n", __func__);
            ret = (-RT_ETIMEOUT);
            break;
        }
    }

    dwmci_writel(host, DWMCI_RINTSTS, mask);

    return ret;
}

static int dwmci_send_cmd(struct dwmci_host *host, struct mmc_cmd *cmd, struct mmc_data *data)
{
    ALLOC_CACHE_ALIGN_BUFFER(struct dwmci_idmac, cur_idmac,
        data ? DIV_ROUND_UP(data->blocks, 8) : 0);
    int ret = 0, flags = 0, i;
    unsigned int timeout = rt_tick_from_millisecond(500);
    rt_uint32_t retry = 100000;
    rt_uint32_t mask, ctrl;
    rt_tick_t start = rt_tick_get();
    struct bounce_buffer bbstate;

    /* wait busy */
    while (dwmci_readl(host, DWMCI_STATUS) & DWMCI_STATUS_DATA_BUSY_MASK)
    {
        if ((rt_tick_get() - start) > timeout)
        {
            rt_kprintf("%s: timeout on data busy\n", __func__);
            return (-RT_ETIMEOUT);
        }
    }

    dwmci_writel(host, DWMCI_RINTSTS, DWMCI_RINTSTS_ALL);

    if (data)
    {
        if (host->fifo_mode)
        {
            dwmci_writel(host, DWMCI_BLKSIZ, data->blocksize);
            dwmci_writel(host, DWMCI_BYTCNT, data->blocksize * data->blocks);
            dwmci_wait_reset(host, DWMCI_CTRL_FIFO_RESET);
        }
        else
        {
            if (data->flags == DATA_DIR_READ)
            {
                bounce_buffer_start(&bbstate, (void *)data->dest, data->blocksize * data->blocks, GEN_BB_WRITE);
            }
            else
            {
                bounce_buffer_start(&bbstate, (void *)data->src, data->blocksize * data->blocks, GEN_BB_READ);
            }
            dwmci_prepare_data(host, data, cur_idmac, bbstate.bounce_buffer);
        }
    }

    dwmci_writel(host, DWMCI_CMDARG, cmd->cmdarg);

    if (data)
    {
        flags |= DWMCI_CMD_DATA_EXPECTED;
        if (data->flags & DATA_DIR_WRITE)
            flags |= DWMCI_CMD_WR;
    }

    if ((cmd->resp_type & MMC_RSP_136) && (cmd->resp_type & MMC_RSP_BUSY))
        return (-RT_ERROR);

    if (cmd->cmdidx == STOP_TRANSMISSION)
        flags |= DWMCI_CMD_STOP_ABORT_CMD;
    else
        flags |= DWMCI_CMD_WAIT_PRVDATA_COMPLETE;

    if (cmd->resp_type & MMC_RSP_PRESENT)
    {
        flags |= DWMCI_CMD_RESPONSE_EXPECT;
        if (cmd->resp_type & MMC_RSP_136)
            flags |= DWMCI_CMD_RESPONSE_LENGTH;
    }

    if (cmd->resp_type & MMC_RSP_CRC)
        flags |= DWMCI_CMD_CHECK_RESPONSE_CRC;

    flags |= (cmd->cmdidx | DWMCI_CMD_START_CMD | DWMCI_CMD_USE_HOLD_REG);
    dwmci_writel(host, DWMCI_CMD, flags);

    // rt_kprintf("sending cmd %d\n",cmd->cmdidx);

    for (i = 0; i < retry; i++)
    {
        mask = dwmci_readl(host, DWMCI_RINTSTS);
        if (mask & DWMCI_RINTSTS_CMDD_MASK)
        {
            if (!data)
                dwmci_writel(host, DWMCI_RINTSTS, mask);
            break;
        }
    }

    if (i == retry)
    {
        rt_kprintf("%s: timeout.\n", __func__);
        return (-RT_ETIMEOUT);
    }

    if (mask & DWMCI_RINTSTS_RTO_MASK)
    {
        /*
         * Timeout here is not necessarily fatal. (e)MMC cards
         * will splat here when they receive CMD55 as they do
         * not support this command and that is exactly the way
         * to tell them apart from SD cards. Thus, this output
         * below shall be debug(). eMMC cards also do not favor
         * CMD8, please keep that in mind.
         */
        // rt_kprintf("%s: response timeout %d.\n", __func__, cmd->cmdidx);
        return (-RT_ETIMEOUT);
    }
    else if (mask & DWMCI_RINTSTS_RE_MASK)
    {
        rt_kprintf("%s: response error.\n", __func__);
        return (-RT_EIO);
    }

    if (cmd->resp_type & MMC_RSP_PRESENT)
    {
        if (cmd->resp_type & MMC_RSP_136)
        {
            cmd->response[0] = dwmci_readl(host, DWMCI_RESP3);
            cmd->response[1] = dwmci_readl(host, DWMCI_RESP2);
            cmd->response[2] = dwmci_readl(host, DWMCI_RESP1);
            cmd->response[3] = dwmci_readl(host, DWMCI_RESP0);
        }
        else
        {
            cmd->response[0] = dwmci_readl(host, DWMCI_RESP0);
        }
    }

    if (data)
    {
        // rt_kprintf("data = %p, cmd->cmdidx = %d\n", data, cmd->cmdidx);
        ret = dwmci_data_transfer(host, data);

        /* only dma mode need it */
        if (!host->fifo_mode)
        {
            ctrl = dwmci_readl(host, DWMCI_CTRL);
            ctrl &= ~(DWMCI_CTRL_DMA_ENABLE);
            dwmci_writel(host, DWMCI_CTRL, ctrl);
            bounce_buffer_stop(&bbstate);
        }
    }

    // plat_delay(5000);

    return ret;
}

static int dwmci_init(struct dwmci_host *host)
{
    rt_uint32_t value = 0;

    dwmci_writel(host, DWMCI_PWREN, DWMCI_PWREN_ENABLE);

    /* wait reset all */
    if (!dwmci_wait_reset(host, DWMCI_CTRL_RESET_ALL))
    {
        rt_kprintf("%s[%d] fail-reset!!\n", __func__, __LINE__);
        return (-RT_EIO);
    }

    value = dwmci_readl(host, DWMCI_HCON);
    if(DWMCI_GET_TRANS_MODE(value) == DWMCI_HCON_DMA_INTERFACE_NONE)
        host->fifo_mode = RT_FALSE;
    else
        host->fifo_mode = RT_TRUE;

    /* enumerate at 400KHz */
    dwmci_setup_bus(host, 400*1000); // 396.83KHz

    /* config dwmci */
    dwmci_writel(host, DWMCI_RINTSTS, DWMCI_RINTSTS_ALL);           /* clear all interrupt status */
    dwmci_writel(host, DWMCI_INTMASK, 0);                           /* mask all interrupt */
    dwmci_writel(host, DWMCI_TMOUT, 0xFFFFFFFF);                    /* set timeout */
    dwmci_writel(host, DWMCI_IDINTEN, 0);                           /* disable all interrupt */
    dwmci_writel(host, DWMCI_BMOD, 1);                              /* soft reset */
    dwmci_writel(host, DWMCI_FIFOTH, host->fifoth_val);             /* set fifoth */
    dwmci_writel(host, DWMCI_CLKENA, DWMCI_CLKENA_CCLK_DISABLE);    /* disable clcok and non-low-power mode */
    dwmci_writel(host, DWMCI_CLKSRC, 0);

    rt_thread_mdelay(10);

    return RT_EOK;
}

static void request(struct rt_mmcsd_host *h, struct rt_mmcsd_req *req)
{
    struct dwmci_host *host = (struct dwmci_host *)h->private_data;
    struct mmc_cmd cmd, stop;
    struct mmc_data data;

    rt_memset(&cmd,  0, sizeof(struct mmc_cmd));
    rt_memset(&stop, 0, sizeof(struct mmc_cmd));
    rt_memset(&data, 0, sizeof(struct mmc_data));

    cmd.cmdidx = req->cmd->cmd_code;
    cmd.cmdarg = req->cmd->arg;
    cmd.resp_type = dwmci_convert_resp_type(req->cmd->flags);

    if(req->data)
    {
        data.dest = (char *)req->data->buf;
        data.flags = req->data->flags;
        data.blocksize = req->data->blksize;
        data.blocks = req->data->blks;
        req->cmd->err = dwmci_send_cmd(host, &cmd, &data);
    }
    else
    {
        req->cmd->err = dwmci_send_cmd(host, &cmd, RT_NULL);
    }

    req->cmd->resp[3] = cmd.response[3];
    req->cmd->resp[2] = cmd.response[2];
    req->cmd->resp[1] = cmd.response[1];
    req->cmd->resp[0] = cmd.response[0];

    if (req->stop)
    {
        stop.cmdidx = req->stop->cmd_code;
        stop.cmdarg = req->stop->arg;
        stop.resp_type = dwmci_convert_resp_type(req->stop->flags);
        req->cmd->err = dwmci_send_cmd(host, &stop, RT_NULL);
    }

    mmcsd_req_complete(host->host);
}

static void set_iocfg(struct rt_mmcsd_host *h, struct rt_mmcsd_io_cfg *io_cfg)
{
    struct dwmci_host *host = (struct dwmci_host *)h->private_data;
    dwmci_set_ios(host, io_cfg->bus_width, io_cfg->clock);
}

static rt_int32_t get_card_status(struct rt_mmcsd_host *h){ return RT_EOK; }
static void enable_sdio_irq(struct rt_mmcsd_host *h, rt_int32_t en) {}

static const struct rt_mmcsd_host_ops ops =
{
    request,
    set_iocfg,
    get_card_status,
    enable_sdio_irq,
};

int rockchip_hw_dwmci_init(void)
{
    struct dwmci_host *dwmci = RT_NULL;
    struct rt_mmcsd_host *host = RT_NULL;

#if defined(BSP_USING_SDMMC)
    dwmci = &sdmmc;

    iomux_set_iomux_sel(GPIO_PORT_0, GPIO_PIN_A, GPIO_INDEX_4, IOMUX_SEL_0A4_SDMMC0_DET);
    iomux_set_iomux_sel(GPIO_PORT_0, GPIO_PIN_A, GPIO_INDEX_5, IOMUX_SEL_0A5_SDMMC0_PWREN);
    iomux_set_iomux_sel(GPIO_PORT_1, GPIO_PIN_D, GPIO_INDEX_5, IOMUX_SEL_1D5_SDMMC0_D0);
    iomux_set_iomux_sel(GPIO_PORT_1, GPIO_PIN_D, GPIO_INDEX_6, IOMUX_SEL_1D6_SDMMC0_D1);
    iomux_set_iomux_sel(GPIO_PORT_1, GPIO_PIN_D, GPIO_INDEX_7, IOMUX_SEL_1D7_SDMMC0_D2);
    iomux_set_iomux_sel(GPIO_PORT_2, GPIO_PIN_A, GPIO_INDEX_0, IOMUX_SEL_2A0_SDMMC0_D3);
    iomux_set_iomux_sel(GPIO_PORT_2, GPIO_PIN_A, GPIO_INDEX_1, IOMUX_SEL_2A1_SDMMC0_CMD);
    iomux_set_iomux_sel(GPIO_PORT_2, GPIO_PIN_A, GPIO_INDEX_2, IOMUX_SEL_2A2_SDMMC0_CLK);

    // /* init dwmci */
    dwmci->base = (rt_ubase_t)rt_ioremap((void *)0xFE2B0000, 64*1024);
    // dwmci->base = rockchip_regmap_get_addr(dwmci->base);
    dwmci->fifoth_val = DWMCI_FIFOTH_MSIZE_128 |
        DWMCI_RX_WMARK(DWMCI_FIFO_DEPTH / 2 - 1) | DWMCI_TX_WMARK(DWMCI_FIFO_DEPTH / 2);

    /* init mmcsd driver */
    dwmci->host         = mmcsd_alloc_host();
    host                = dwmci->host;
    host->ops           = &ops;
    host->freq_min      = 400 * 1000;
    host->freq_max      = 50 * 1000 * 1000;
    host->valid_ocr     = VDD_32_33 | VDD_33_34;
    host->flags         = MMCSD_BUSWIDTH_4 | MMCSD_MUTBLKWRITE | MMCSD_SUP_SDIO_IRQ | MMCSD_SUP_HIGHSPEED;
    host->private_data  = dwmci;

    /* be ready */
    rt_strncpy(host->name, dwmci->name, rt_strnlen(dwmci->name, RT_NAME_MAX-1));
    host->name[rt_strnlen(dwmci->name, RT_NAME_MAX-1)] = '\0';

    dwmci_init(dwmci);
    mmcsd_change(host);
#endif /* BSP_USING_SDMMC */

#if defined(BSP_USING_EMMC)
    dwmci = &emmc;

    /* init dwmci */
    // dwmci->base = (rt_ubase_t)rt_ioremap((void *)dwmci->base, 64*1024);
    dwmci->base = rockchip_regmap_get_addr(dwmci->base);
    dwmci->fifoth_val = DWMCI_FIFOTH_MSIZE_128 |
        DWMCI_RX_WMARK(DWMCI_FIFO_DEPTH / 2 - 1) | DWMCI_TX_WMARK(DWMCI_FIFO_DEPTH / 2);

    /* init mmcsd driver */
    dwmci->host         = mmcsd_alloc_host();
    host                = dwmci->host;
    host->ops           = &ops;
    host->freq_min      = 400 * 1000;
    host->freq_max      = 52 * 1000 * 1000; /* SDR52 */
    host->valid_ocr     = VDD_165_195 | VDD_32_33 | VDD_33_34;
    host->flags         = MMCSD_BUSWIDTH_8 | MMCSD_MUTBLKWRITE | MMCSD_SUP_SDIO_IRQ | MMCSD_SUP_HIGHSPEED;
    host->private_data  = dwmci;

    /* be ready */
    rt_strncpy(host->name, dwmci->name, rt_strnlen(dwmci->name, RT_NAME_MAX-1));
    host->name[rt_strnlen(dwmci->name, RT_NAME_MAX-1)] = '\0';

    dwmci_init(dwmci);
    mmcsd_change(host);
#endif /* BSP_USING_EMMC */

    return RT_EOK;
}
INIT_DEVICE_EXPORT(rockchip_hw_dwmci_init);
MSH_CMD_EXPORT_ALIAS(rockchip_hw_dwmci_init, rockchip_hw_dwmci_init, rockchip_hw_dwmci_init);
